import { isArray } from "element-plus/es/utils/types.mjs";
import { MessageTreeDataType, SecsFormat } from "./enums";
import { MessageValue, MessageTreeData, Message, MessageTransactionData, MessageTransaction } from "./types";
import Node from "element-plus/lib/components/tree/src/model/node.js";
import { nanoid } from "nanoid";

let str = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
function getRandomInt(max: number): number {
    return Math.floor(Math.random() * max);
}

function parseValueToStringCommon(value: any, func: (arg: any, index?: number) => any) {
    if (isArray(value)) {
        let result = "";
        for (let i = 0; i < value.length; i++) {
            if (i != 0) {
                result += " ";
            }
            result += func(value[i])
        }
        return result;
    }
    else {
        return func(value);
    }
}

function parseStringToValueCommon(str: String | null, func: (arg: any, index?: number) => any) {
    if (str == "" || str == null) {
        return [];
    }
    let array = str.split(/[ ,;]/g);
    let result = [];
    for (var i = 0; i < array.length; i++) {
        result.push(func(array[i].trim(), i));
    }
    return result;
}
const regInt: RegExp = /^\d+$/;
const regFloat: RegExp = /^\d+(?:.+\d)*$/;

const helper = {
    F4_MIN: -3.4028235E38,
    F4_MAX: 3.4028235E38,
    F8_MIN: -1.79E+308,
    F8_MAX: 1.79E+308,
    U8_MIN: 0,
    U8_MAX: Math.pow(2, 64) - 1,
    U4_MIN: 0,
    U4_MAX: 4294967295,
    U2_MIN: 0,
    U2_MAX: 65535,
    U1_MIN: 0,
    U1_MAX: 255,
    I8_MIN: - Math.pow(2, 63),
    I8_MAX: Math.pow(2, 63) - 1,
    I4_MIN: -2147483648,
    I4_MAX: 2147483647,
    I2_MIN: -32768,
    I2_MAX: 32767,
    I1_MIN: -128,
    I1_MAX: 127,
    globalId: 0,
    // LEN_MAX: 2 ^ 24 - 1,
    parseSecsFormatFromString: (format: string): SecsFormat => {
        switch (format.toLowerCase()) {
            case "list":
            case "l":
                return SecsFormat.List;
            case "ascii":
                return SecsFormat.AscII;
            case "jis8":
                return SecsFormat.Jis8;
            case "binary":
                return SecsFormat.Binary;
            case "boolean":
                return SecsFormat.Boolean;
            case "f4":
                return SecsFormat.F4;
            case "f8":
                return SecsFormat.F8;
            case "i1":
                return SecsFormat.I1;
            case "i2":
                return SecsFormat.I2;
            case "i4":
                return SecsFormat.I4;
            case "i8":
                return SecsFormat.I8;
            case "u1":
                return SecsFormat.U1;
            case "u2":
                return SecsFormat.U2;
            case "u4":
                return SecsFormat.U4;
            case "u8":
                return SecsFormat.U8;
            default:
                return SecsFormat.Unknow;
        };
    },
    isPercent: (value: string): boolean => {
        return /^\d+.?\d*%$/g.test(value);
    },
    parseFloat: (value: string): number => {
        var s = parseFloat(value);
        if (isNaN(s)) {
            return 0;
        }
        return s;
    },
    onSelectStart: (evt: Event) => {
        evt.preventDefault();
    },
    genRandomString: (len?: number) => {
        if (len == null) {
            len = 6;
        }
        let result = "";
        for (let i = 0; i < len; i++) {
            result += str[getRandomInt(str.length)];
        }
        return result;
    },
    isArray: (value: any): boolean => {
        return Array.isArray(value);
    },
    parseToBoolean(value: any, index?: number): boolean {
        if (value === true || value == "1" || value.toString().toLowerCase() == "true") {
            return true;
        }
        else if (value === false || value == "0" || value.toString().toLowerCase() == "false") {
            return false;
        }
        else if (index == null) {
            throw new Error(`the value = [${value}] is invalid for Boolean format`);
        }
        else {
            throw new Error(`the value = [${value}] at index ${index} is invalid for Boolean format`);
        }
    },
    // parseToBinary(value: any, index?: number): number {
    //     if (value === 0 || value === 1) {
    //         return value;
    //     }
    //     if (value == "0" || value == "1") {
    //         return parseFloat(value);
    //     }
    //     else if (index == null) {
    //         throw new Error(`the value = [${value}] is invalid for Binary format`);
    //     }
    //     else {
    //         throw new Error(`the value = [${value}] at index ${index} is invalid for Binary format`);
    //     }
    // },
    parseValueToString: (value: any, format: SecsFormat): string | null => {
        if (value == null) {
            return null;
        }
        switch (format) {
            case SecsFormat.Unknow:
            case SecsFormat.List:
                return null;
            case SecsFormat.AscII:
            case SecsFormat.Jis8:
                return value?.toString() ?? "";
            case SecsFormat.Binary:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        // return helper.parseToBinary(arg, index).toString();
                        return helper.parseToNumber(arg, null, helper.check_u1_range, "Binary", index);
                    });
                }
            case SecsFormat.Boolean:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToBoolean(arg, index).toString();
                    });
                }
            case SecsFormat.F4:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_f8_range, "F8", index);
                    });
                }
            case SecsFormat.F8:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_f4_range, "F4", index);
                    });
                }
            case SecsFormat.I1:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_i1_range, "I1", index);
                    });
                }
            case SecsFormat.I2:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_i2_range, "I2", index);
                    });
                }
            case SecsFormat.I4:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_i4_range, "I4", index);
                    });
                }
            case SecsFormat.I8:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_i8_range, "I8", index);
                    });
                }
            case SecsFormat.U1:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_u1_range, "U1", index);
                    });
                }
            case SecsFormat.U2:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_u2_range, "U2", index);
                    });
                }
            case SecsFormat.U4:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_u4_range, "U4", index);
                    });
                }
            case SecsFormat.U8:
                {
                    return parseValueToStringCommon(value, (arg: any, index?: number): any => {
                        return helper.parseToNumber(arg, null, helper.check_u8_range, "U8", index);
                    });
                }
        }
    }, parseValueToStringWithoutCheck: (value: any, format: SecsFormat): string => {
        if (value == null) {
            return "";
        }
        switch (format) {
            case SecsFormat.Unknow:
            case SecsFormat.List:
                return "";
            case SecsFormat.AscII:
            case SecsFormat.Jis8:
                return value?.toString() ?? "";
            case SecsFormat.Binary:
            case SecsFormat.Boolean:
            case SecsFormat.F4:
            case SecsFormat.F8:
            case SecsFormat.I1:
            case SecsFormat.I2:
            case SecsFormat.I4:
            case SecsFormat.I8:
            case SecsFormat.U1:
            case SecsFormat.U2:
            case SecsFormat.U4:
            case SecsFormat.U8:
                {
                    return value.join(" ");
                }
        }
    },
    parseStringToValue: (str: string | null, format: SecsFormat): any => {
        switch (format) {
            case SecsFormat.Unknow:
                return null;
            case SecsFormat.List:
                return [];
            case SecsFormat.AscII:
            case SecsFormat.Jis8:
                return str || "";
            case SecsFormat.Binary:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_u1_range, "Binary", index);
                });
            case SecsFormat.Boolean:
                return parseStringToValueCommon(str, helper.parseToBoolean);
            case SecsFormat.F8:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regFloat, helper.check_f8_range, "F8", index);
                });
            case SecsFormat.F4:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regFloat, helper.check_f4_range, "F4", index);
                });
            case SecsFormat.I8:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_i8_range, "I8", index);
                });
            case SecsFormat.I4:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_i4_range, "I4", index);
                });
            case SecsFormat.I2:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_i2_range, "I2", index);
                });
            case SecsFormat.I1:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_i1_range, "I1", index);
                });
            case SecsFormat.U8:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_u8_range, "U8", index);
                });
            case SecsFormat.U4:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_u4_range, "U4", index);
                });
            case SecsFormat.U2:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_u2_range, "U2", index);
                });
            case SecsFormat.U1:
                return parseStringToValueCommon(str, (arg: any, index?: number): any => {
                    return helper.parseToNumber(arg, regInt, helper.check_u1_range, "U1", index);
                });
        }
    },
    parseToNumber: (value: any, reg: RegExp | null, check_range: (num: number) => boolean, format: string, index?: number): number => {
        let result = reg == null ? true : reg.test(value);
        if (result) {
            let val = parseFloat(value);
            if (!isNaN(val)) {
                if (!check_range(val)) {
                    if (index == null) {
                        throw new Error(`the value = [${value}] is out of  ${format} format`);
                    }
                    else {
                        throw new Error(`the value = [${value}] at index ${index} is out of ${format} format`);
                    }
                }
                return val;
            }
            result = false;
        }
        if (!result) {
            if (index == null) {
                throw new Error(`the value = [${value}] is invalid for ${format} format`);
            }
            else {
                throw new Error(`the value = [${value}] at index ${index} is invalid for ${format} format`);
            }
        }
        return 0;
    },
    check_f4_range: (value: number): boolean => {
        return value >= helper.F4_MIN && value <= helper.F4_MAX;
    },
    check_f8_range: (value: number): boolean => {
        return value >= helper.F8_MIN && value <= helper.F8_MAX;
    },
    check_i8_range: (value: number): boolean => {
        return value >= helper.I8_MIN && value <= helper.I8_MAX;
    },
    check_i4_range: (value: number): boolean => {
        return value >= helper.I4_MIN && value <= helper.I4_MAX;
    },
    check_i2_range: (value: number): boolean => {
        return value >= helper.I2_MIN && value <= helper.I2_MAX;
    },
    check_i1_range: (value: number): boolean => {
        return value >= helper.I1_MIN && value <= helper.I1_MAX;
    },
    check_u8_range: (value: number): boolean => {
        return value >= helper.U8_MIN && value <= helper.U8_MAX;
    },
    check_u4_range: (value: number): boolean => {
        return value >= helper.U4_MIN && value <= helper.U4_MAX;
    },
    check_u2_range: (value: number): boolean => {
        return value >= helper.U2_MIN && value <= helper.U2_MAX;
    },
    check_u1_range: (value: number): boolean => {
        return value >= helper.U1_MIN && value <= helper.U1_MAX;
    },
    analysisMessageValue: (value: MessageValue, isRoot: boolean): MessageTreeData => {
        let item: MessageTreeData = {
            id: (++helper.globalId).toString(),
            name: value.name,
            desc: value.desc,
            children: [],
            tag: { type: isRoot ? MessageTreeDataType.RootValue : MessageTreeDataType.Value, mappingRef: null, value: null, format: helper.parseSecsFormatFromString(value.format) },
            valueString: null,
        };
        if ("format" in item.tag && item.tag.format == SecsFormat.List) {
            if (value.value != null && Array.isArray(value.value)) {
                for (let i = 0; i < value.value.length; i++) {
                    let valueItem = value.value[i] as MessageValue;
                    item.children?.push(helper.analysisMessageValue(valueItem, false));
                }
            }
        }
        else {
            item.valueString = helper.parseValueToString(value.value, value.format);
            if ("value" in item.tag) {
                item.tag.value = value.value;
            }
        }
        return item;
    },
    duplicateTreeData: (item: MessageTreeData, isRegenerateId: boolean): MessageTreeData | null => {
        if (item == null) {
            return null;
        }
        let result = { ...item } as MessageTreeData;
        if (isRegenerateId) {
            result.id = (++helper.globalId).toString();
        }
        if (result.tag != null) {
            result.tag = {} as any;
            let temp = result.tag as any;
            let temp1 = item.tag as any;
            for (let i in temp1) {
                temp[i] = temp1[i] as any;
            }
        }
        if (item.children != undefined && item.children != null && item.children.length > 0) {
            result.children = [];
            for (let i = 0; i < item.children.length; i++) {
                let childItem = helper.duplicateTreeData(item.children[i], isRegenerateId);
                if (childItem != null) {
                    result.children.push(childItem);
                }
            }
        }
        else {
            result.children = [];
        }
        return result;
    },
    newEmptyValueTreeItem: (): MessageTreeData => {
        return {
            id: (++helper.globalId).toString(),
            name: '',
            desc: '',
            children: [],
            valueString: null,
            tag: { type: MessageTreeDataType.Value, value: [], mappingRef: null, format: SecsFormat.List }
        };
    },
    parseToMessageTreeData: (message: Message, dataType: MessageTreeDataType.Primary | MessageTreeDataType.Secondary, time?: string): MessageTreeData => {
        let item: MessageTreeData = {
            id: (++helper.globalId).toString(),
            name: message.name,
            desc: message.desc || "",
            tag: {
                type: dataType,
                s: message.head.s,
                f: message.head.f,
                isReply: message.head.isExpectReply,
                systemByte: message.head.systemByte ?? 0,
                time: time
            },
            children: null,
            valueString: null,
        };
        if (message.body != null) {
            item.children = [];
            item.children.push(helper.analysisMessageValue(message.body, true));
        }
        return item;
    },
    parseToMessage: (item: MessageTreeData | null): Message | null => {
        if (item == null || !("s" in item.tag)) {
            return null;
        }
        return {
            name: item.name,
            desc: item.desc,
            head: {
                s: item.tag.s,
                f: item.tag.f,
                systemByte: item.tag.systemByte,
                isExpectReply: item.tag.isReply
            },
            body: helper.readRootValue(item.children),
        };
    },
    readRootValue: (data: MessageTreeData[] | null,): MessageValue | null => {
        if (data == null || data.length == 0) {
            return null;
        }
        return helper.readValue(data[0]);
    },
    readValue: (data: MessageTreeData): MessageValue => {
        if (data.tag == null || !("value" in data.tag)) {
            return {} as MessageValue;
        }
        let result: MessageValue = {
            name: data.name,
            desc: data.desc,
            format: data.tag.format == null ? SecsFormat.Unknow : data.tag.format,
            mappingRef: data.tag.mappingRef,
            value: null
        };
        if (result.format == SecsFormat.List) {
            result.value = [];
            if (data.children != null && data.children.length > 0) {
                for (let i = 0; i < data.children.length; i++) {
                    result.value.push(helper.readValue(data.children[i]));
                }
            }
        }
        else if (data.tag.value != null) {
            if (Array.isArray(data.tag.value)) {
                result.value = [...data.tag.value];
            }
            else {
                result.value = data.tag.value;
            }
        }
        return result;
    },
    getOffsetTop: (item: HTMLElement): number => {
        let top = item.offsetTop;
        let parent = item.offsetParent as any;
        while (parent != null && parent != document.body) {
            top += parent.offsetTop;
            parent = parent.offsetParent;
        }
        return top;
    },
    getLogLevelText: (level: number): string => {
        switch (level) {
            case 0:
                return "Trace";
            case 1:
                return "Debug";
            case 2:
                return "Info";
            case 3:
                return "Warn";
            default:
                return "Error";
        }
    },
    getConnectionStatusText: (level: number): string => {
        switch (level) {
            case 0:
                return "Initial";
            case 1:
                return "Connecting";
            case 2:
                return "Connected";
            case 3:
                return "Selected";
            case 4:
                return "Deselected";
            case 5:
                return "Reconnecting";
            case 6:
                return "Disconnected";
            default:
                return "Shutdown";
        }
    },
    parseToTransaction: (data: MessageTreeData): MessageTransaction => {
        let primary = null;
        let secondary = null;
        if (data.children != null) {
            let length = data.children!.length;
            if (length > 0) {
                primary = helper.parseToMessage(data.children[0]);
            }
            if (length > 1) {
                secondary = helper.parseToMessage(data.children[1]);
            }
        }
        let mt: MessageTransaction = {
            id: data.id,
            name: data.name,
            desc: data.desc,
            primary: primary!,
            secondary: secondary,
            reply_secondary: true,
            match_rules: [],
            send_primary: []
        };
        if (("receiveAction" in data.tag)) {
            mt.match_rules = data.tag.receiveAction.matchRules;
            mt.reply_secondary = data.tag.receiveAction.replySecondary;
            mt.send_primary = data.tag.receiveAction.perforAction.map(x => x.transactionName);
        }
        return mt;
    },
    parseToMessageTreeDataFull: (data: MessageTransaction): MessageTreeData => {
        let item: MessageTreeData = {
            id: data.id,
            name: data.name,
            desc: data.desc || "",
            children: [],
            tag: {
                type: MessageTreeDataType.Transaction,
                receiveAction: {
                    matchRules: [],
                    replySecondary: true,
                    perforAction: []
                }
            },
            valueString: null,
        };
        if (data.primary) {
            item.children?.push(helper.parseToMessageTreeData(data.primary, MessageTreeDataType.Primary))
        }
        if (data.secondary != null) {
            item.children?.push(helper.parseToMessageTreeData(data.secondary as Message, MessageTreeDataType.Secondary))
        }
        return item;
    },
    parseToTransactionData: (data: MessageTreeData): MessageTransactionData => {
        let primary = null;
        let secondary = null;
        if (data.children != null) {
            let length = data.children!.length;
            if (length > 0) {
                primary = data.children![0];
            }
            if (length > 1) {
                secondary = data.children![1];
            }
        }
        return {
            name: data.name,
            desc: data.desc,
            primary: primary!,
            secondary: secondary,
        };
    },
    findMatchedTransaction: (source: MessageTreeData[], target: MessageTreeData, isIgnoreType: boolean, isIgnoreValue: boolean): MessageTransactionData | null => {
        if (!("s" in target.tag)) {
            return null;
        }
        let matched: MessageTreeData | null = null;
        for (var i = 0; i < source.length; i++) {
            if (source[i].children?.length == 0) {
                continue;
            }
            let primary = source[i].children![0];
            if (primary.tag == null || !("s" in primary.tag)) {
                continue;
            }
            if (primary.tag.s == target.tag.s && primary.tag.f == target.tag.f) {
                // 判断数据格式是否相等，如果相等，则赋值上数据
                let isSourceExistsBody = false, isTargetExistsBody = false;
                if (primary.children != null && primary.children.length > 0) {
                    isSourceExistsBody = true;
                }
                if (target.children != null && target.children.length > 0) {
                    isTargetExistsBody = true;
                }
                if (isSourceExistsBody == true && isTargetExistsBody == true) {
                    let isMatched = helper.isMessageValueMatched(primary.children![0], target.children![0], isIgnoreType, isIgnoreValue);
                    if (isMatched) {
                        return helper.parseToTransactionData(source[i]);
                    }
                    else if (matched == null) {
                        matched = source[i];
                    }
                }
                else if (matched == null) {
                    // 没有 match 中，继续比较下一个，将此结果记录在相似度结果集中
                    matched = source[i];
                }
                else {
                    // match 中，不比较下一个了
                    return helper.parseToTransactionData(source[i]);
                }
            }
        }
        if (matched == null) {
            return null;
        }
        else {
            return helper.parseToTransactionData(matched);
        }
    },
    isMessageValueMatched: (source: MessageTreeData, target: MessageTreeData, isIgnoreType: boolean, isIgnoreValue: boolean): boolean => {
        let isMatched = false;
        if ((source.tag.type == MessageTreeDataType.RootValue || source.tag.type == MessageTreeDataType.Value) && (target.tag.type == MessageTreeDataType.RootValue || target.tag.type == MessageTreeDataType.Value)) {
            const sourceChildrenLen = source.children != null ? source.children.length : 0;
            const targetChildrenLen = target.children != null ? target.children.length : 0;
            if (sourceChildrenLen != targetChildrenLen) {
                isMatched = false;
            }
            else if (isIgnoreType && isIgnoreValue) {
                isMatched = true;
            }
            else if (isIgnoreType) {
                // 比较value
                isMatched = isSecsValueEqual(source.tag.value, target.tag.value);
            }
            else if (isIgnoreValue) {
                isMatched = source.tag.format == target.tag.format;
            }
            else {
                isMatched = source.tag.format == target.tag.format && isSecsValueEqual(source.tag.value, target.tag.value);
            }
            if (isMatched && sourceChildrenLen > 0 && targetChildrenLen > 0) {
                for (var i = 0; i < sourceChildrenLen; i++) {
                    isMatched = isMatched && helper.isMessageValueMatched(source.children![i], target.children![i], isIgnoreType, isIgnoreValue);
                    if (!isMatched) {
                        break;
                    }
                }
            }
        }
        return isMatched;
    },
    fillTreeData: (data: MessageTreeData, message: MessageTreeData) => {
        if ("s" in data.tag && "s" in message.tag) {
            if (data.tag.s != message.tag.s || data.tag.f != message.tag.f) {
                return;
            }
        }
        data.name = message.name;
        data.desc = message.desc;
        if (data.children != null && data.children.length > 0 && message.children != null && message.children.length > 0) {
            for (var i = 0; i < data.children.length; i++) {
                if (message.children.length > i) {
                    helper.fillTreeData(data.children[i], message.children![i]);
                }
                else {
                    break;
                }
            }
        }
    },
    expandNode: (node: Node, isExpandChildren: boolean) => {
        node.expand();
        if (isExpandChildren) {
            for (let i = 0; i < node.childNodes.length; i++) {
                helper.expandNode(node.childNodes[i], isExpandChildren);
            }
        }
    },

    getTimeStr: () => {
        // a.toString().padStart(2,"0")
        let now = new Date();
        return `${now.getFullYear()}`;
    },
    sleep: (ms: number) => {
        return new Promise(resolve => setTimeout(resolve, ms));
    },
    getUniqueId: () => {
        return nanoid(10);
    }
}

const isSecsValueEqual = (source: any, target: any): boolean => {
    if (source == null || target == null) {
        return true;
    }
    const isSourceArray = Array.isArray(source);
    const isTargetArray = Array.isArray(source);
    if (isSourceArray != isTargetArray) {
        return false;
    }
    if (isSourceArray && isTargetArray) {
        if (source.length != target.length) {
            return false;
        }
        for (var i = 0; i < source.length; i++) {
            if (source[i] !== target[1]) {
                return false;
            }
        }
        return true;
    }
    else {
        return source === target;
    }
}

export default helper;